#######################################################
# The class of Simulatable
# Author: moonho.hwang@gmail.com
#   Date: Oct.19.2011
#######################################################
from DEVSInterfaces import *
from DEVSTime import *
from DEVSBase import * 

class Simulatable(TES, XY, Manager):
   '''Simulatable using Classic Discrete Event System Specification (DEVS).'''
   def __init__(self, name, parent=None, tl=0,tn=float('inf')):
      '''constructor'''
      TES.__init__(self, name, parent)
      XY.__init__(self)
      Manager.__init__(self, tl, tn)

      #-- 4/2/2012 
      self.t_last_update = tl
      self.StatePercentage = {}
      #self.bCollectingStatistics = False

   def delta_0(self):
      '''virtual function delta_0: making the initial state'''
      #raise Exception("Method %s::%s must be overridden!"%(classname(self), __function__() ))
      
   def ta(self):
      '''virtual function ta:S -> [0,\infty]: time advance function that returns the remaining time \sigma.'''
      #raise Exception("Method %s::%s must be overridden!"%(classname(self), __function__() ))
      return self.timeAdvance() 

   def delta_x(self, x):
      '''virtual function delta_x: Q x X -> S where Q={(s,e):s \in S, e \in [0,ta(s)]\cap T} is the total state set.'''
      #raise Exception("Method %s::%s must be overridden!"%(classname(self), __function__() ))
	
   def delta_y(self):
      '''virtual function delta_y: S -> Y \cup {\phi} x S where \phi not \in Y is the unobservable event. It combines 
      \lambda: S -> Y\cup{\phi} and \delta_int:S -> S of Zeigler's definitions into one for simplicity'''
      #raise Exception("Method %s::%s must be overridden!"%(classname(self), __function__() ))
   
   def Delta_0(self, t):
      '''reset state and clock of the simulatable'''
      Clock.tNow = t 
      self.t_last_update = t
      self.StatePercentage = {}

      self.delta_0()
      self.set_timeLast(t)
      self.set_timeNext(t + self.ta())

   def Delta_x(self, x, t):
      '''inject (x,t) into the Simulatable to generate next state'''
      if t < self.timeLast() or t> self.timeNext(): 
         raise Exception("Delta_x scheduling error tL=%s, t=%s, tN=%s"%(self.timeLast(), t, self.timeNext()) )
      self.Delta_null(t) # before change the state, compute statistics 
      self.delta_x(x)
      self.set_timeLast(t)
      self.set_timeNext(t + self.ta())

   def Delta_y(self, t):
      '''generate (y,t) and next state from the Simulatable'''
      if t != self.timeNext(): 
         raise Exception("Delta_y scheduling error: t=%s, tN=%s"%(t, self.tN()))
      #print "t=%f, tNow=%f"%(t, Clock.tNow)

      self.Delta_null(t) # before change the state, compute statistics 
      y = self.delta_y()
      self.set_timeLast(t)
      self.set_timeNext(t + self.ta())
      return y

#   def state_to_statistics_str(self):
#      '''convert the state to a statistics string'''
#      return self.state_to_s()

   def Delta_null(self, t):
      '''update the state including statistics at time t'''
      self.updateStatistics(t) 

   def updateStatistics(self, t):
      '''update statistics at time t'''
      s = self.get_statistics_state()
      if s not in self.StatePercentage: self.StatePercentage[s]=0.0

      t_last_update = max(self.t_last_update, self.timeLast())

      self.StatePercentage[s] = self.StatePercentage[s] + (t - t_last_update) # add time difference to [s]

      self.t_last_update = t

   def get_statistics_state(self):
      '''get the current state in terms of statistics'''
      return self.state_to_s() 

   def get_statistics_str(self):
      '''get the string of statistics'''
      total_t = 0
      for k in self.StatePercentage:
	      total_t += self.StatePercentage[k]
	    
#      s = "total_t=%.3f,"%total_t 
      s=""
      if total_t >0 :
         for k in self.StatePercentage:
            s += "P(%s)=%.3f,"%(k, self.StatePercentage[k]/total_t)
      return s

   def __str__(self):
      '''convert the entier model to a string'''
      s = TES.__str__(self) + "\n"
      s += XY.to_IP_s(self) + "\n"
      s += XY.to_OP_s(self) + "\n"
      s += "clock="+ str(self.clock) + "\n"
      return s

   def state_to_s(self):
      '''convert the state to a string'''
      return ""

   def qstate_to_s(self):
      '''convert the total state to a string'''
      s = self.state_to_s()
      if len(s)>0: q = "("+str(self.name)+":"+s+","+str(self.clock)+")"
      else: q ="("+str(self.name)+":"+str(self.clock)+")" 
      return q

class DEVS(Simulatable):
   '''DEVS class '''
   def __init__(self, name, parent=None, tl=0,tn=float('inf')):
      '''constructor'''
      Simulatable.__init__(self, name, parent, tl, tn)

   def delta_x(self, x):
      '''external transition function calling delta_ext(x)'''
      self.delta_ext(x)

   def delta_y(self):
      '''output and internal transition function calling lambda and delta_int()'''
      y = self._lambda()
      self.delta_int()
      return y

if __name__ == '__main__':
   d = Simulatable("hmh")
   d.Delta_0(0)
   print d
   print d.qstate_to_s()
